package utils

/*
 +----------------------------------------------------------------------
 + Title        : InitSentinel
 + Author       : huwl
 + Version      : 1.0.0
 + Initial-Time : 2021-09-22
 + Last-time    : 这个文件最后修改的时间 + 修改人的名称
 + Desc         : 生产者可靠性
 +----------------------------------------------------------------------
*/
import (
	"log"

	sentinel "github.com/alibaba/sentinel-golang/api"
	"github.com/alibaba/sentinel-golang/core/circuitbreaker"
	"github.com/alibaba/sentinel-golang/core/flow"
)

var LimiterResource string
var CircuitbreakerResource string

func init() {
	// 初始化 sentinel
	err := sentinel.InitWithConfigFile("config/sentinel.yaml")
	if err != nil {
		log.Fatalf("Failed to sentinel InitWithConfigFile: %v", err)
	}

	// 读取limiter配置
	limiterConfig, err := GetConfig("limiter", "yml")
	if err != nil {
		log.Fatalf("Failed to limiter GetConfig: %v", err)
	}

	limiterType := limiterConfig.GetString("type")

	LimiterResource = limiterConfig.GetString(limiterType + ".resource")

	// 当前流量控制器的Token计算策略。
	var tokenCalculateStrategy flow.TokenCalculateStrategy
	tcs := limiterConfig.GetInt(limiterType + ".tokenCalculateStrategy")
	if tcs == 0 {
		tokenCalculateStrategy = flow.Direct
	} else if tcs == 1 {
		tokenCalculateStrategy = flow.WarmUp
	}

	// 表示流量控制器的控制策略
	var controlBehavior flow.ControlBehavior
	cb := limiterConfig.GetInt(limiterType + ".controlBehavior")
	if cb == 0 {
		controlBehavior = flow.Reject
	} else if cb == 1 {
		controlBehavior = flow.Throttling
	}

	// ----------------------- go语言实现暂未发现appLimit即限制客户端每秒请求次数 -----------------------
	if _, err := flow.LoadRules([]*flow.Rule{
		// Resource：资源名，即规则的作用目标。
		// TokenCalculateStrategy: 当前流量控制器的Token计算策略。Direct表示直接使用字段 Threshold 作为阈值；WarmUp表示使用预热方式计算Token的阈值。
		// ControlBehavior: 表示流量控制器的控制策略；Reject表示超过阈值直接拒绝，Throttling表示匀速排队。
		// Threshold: 表示流控阈值；如果字段 StatIntervalInMs 是1000(也就是1秒)，那么Threshold就表示QPS，流量控制器也就会依据资源的QPS来做流控。
		// RelationStrategy: 调用关系限流策略，CurrentResource表示使用当前规则的resource做流控；AssociatedResource表示使用关联的resource做流控，关联的resource在字段 RefResource 定义；
		// RefResource: 关联的resource；
		// WarmUpPeriodSec: 预热的时间长度，该字段仅仅对 WarmUp 的TokenCalculateStrategy生效；
		// WarmUpColdFactor: 预热的因子，默认是3，该值的设置会影响预热的速度，该字段仅仅对 WarmUp 的TokenCalculateStrategy生效；
		// MaxQueueingTimeMs: 匀速排队的最大等待时间，该字段仅仅对 Throttling ControlBehavior生效；
		// StatIntervalInMs: 规则对应的流量控制器的独立统计结构的统计周期。如果StatIntervalInMs是1000，也就是统计QPS。
		{
			Resource:               LimiterResource,                                            //资源名
			Threshold:              limiterConfig.GetFloat64(limiterType + ".threshold"),       //表示流控阈值；如果字段 StatIntervalInMs 是1000(也就是1秒)，那么Threshold就表示QPS，流量控制器也就会依据资源的QPS来做流控
			StatIntervalInMs:       limiterConfig.GetUint32(limiterType + ".statIntervalInMs"), //规则对应的流量控制器的独立统计结构的统计周期。如果StatIntervalInMs是1000，也就是统计QPS
			TokenCalculateStrategy: tokenCalculateStrategy,                                     // flow.Direct
			ControlBehavior:        controlBehavior,                                            //flow.Reject
		},
	}); err != nil {
		log.Fatalf("Failed to flow LoadRules: %v", err)
	}

	// -------------------------------------------------------
	// ----------------------- 熔断开始 -----------------------
	//读取 circuitbreaker 配置
	circuitbreakerConfig, err := GetConfig("circuitbreaker", "yml")
	if err != nil {
		log.Fatalf("Failed to circuitbreaker GetConfig: %v", err)
	}

	circuitbreakerType := circuitbreakerConfig.GetString("type")
	CircuitbreakerResource = circuitbreakerConfig.GetString(circuitbreakerType + ".resource")
	var circuitbreakerStrategy circuitbreaker.Strategy
	if circuitbreakerType == "slow-ratio-circuitbreaker" {
		circuitbreakerStrategy = circuitbreaker.SlowRequestRatio
	} else if circuitbreakerType == "error-ratio-circuitbreaker" {
		circuitbreakerStrategy = circuitbreaker.ErrorRatio
	} else {
		circuitbreakerStrategy = circuitbreaker.ErrorCount
	}

	if _, err := circuitbreaker.LoadRules([]*circuitbreaker.Rule{
		{
			// 熔断器规则生效的埋点资源的名称；
			Resource: CircuitbreakerResource,
			// 选择以慢调用比例 (SlowRequestRatio) 作为阈值，需要设置允许的最大响应时间（MaxAllowedRtMs），请求的响应时间大于该值则统计为慢调用。
			// 通过 Threshold 字段设置触发熔断的慢调用比例，取值范围为 [0.0, 1.0]。
			// 在单位统计时长内请求数目大于设置的最小请求数目，并且慢调用的比例大于阈值，则接下来的熔断时长内请求会自动被熔断。
			// 经过熔断时长后熔断器会进入探测恢复状态，若接下来的一个请求响应时间小于设置的最大 RT 则结束熔断，
			// 若大于设置的最大 RT 则会再次被熔断。
			Strategy: circuitbreakerStrategy,
			// 即熔断触发后持续的时间（单位为 ms）。
			// 资源进入熔断状态后，在配置的熔断时长内，请求都会快速失败。
			// 熔断结束后进入探测恢复模式（HALF-OPEN）。
			RetryTimeoutMs: circuitbreakerConfig.GetUint32(circuitbreakerType + ".retryTimeoutMs"),
			// 静默数量，触发熔断的最小请求数目，若当前统计周期内的请求数小于此值，即使达到熔断条件规则也不会触发。
			MinRequestAmount: circuitbreakerConfig.GetUint64(circuitbreakerType + ".minRequestAmount"),
			// 统计的时间窗口长度（单位为 ms）。
			StatIntervalMs: circuitbreakerConfig.GetUint32(circuitbreakerType + ".statIntervalMs"),
			// 仅对慢调用熔断策略生效，MaxAllowedRtMs 是判断请求是否是慢调用的临界值，
			// 也就是如果请求的response time小于或等于MaxAllowedRtMs，那么就不是慢调用；
			// 如果response time大于MaxAllowedRtMs，那么当前请求就属于慢调用。
			MaxAllowedRtMs: circuitbreakerConfig.GetUint64(circuitbreakerType + ".maxAllowedRtMs"),
			// 对于慢调用熔断策略, Threshold表示是慢调用比例的阈值(小数表示，比如0.1表示10%)，
			// 也就是如果当前资源的慢调用比例如果高于Threshold，那么熔断器就会断开；否则保持闭合状态。
			// 对于错误比例策略，Threshold表示的是错误比例的阈值(小数表示，比如0.1表示10%)。
			// 对于错误数策略，Threshold是错误计数的阈值。
			Threshold: circuitbreakerConfig.GetFloat64(circuitbreakerType + ".threshold"),
		},
	}); err != nil {
		log.Fatalf("Failed to circuitbreaker LoadRules: %v", err)
	}
	// ----------------------- 熔断结束 -----------------------
	// -------------------------------------------------------

	// ----------------------- 并发隔离控制 -----------------------
	// _, err = isolation.LoadRules([]*isolation.Rule{
	// 	{
	// 		Resource:   "request-isolation",
	// 		MetricType: isolation.Concurrency,
	// 		Threshold:  12,
	// 	},
	// })
	// if err != nil {
	// 	log.Fatalf("Unexpected error: %+v", err)
	// 	return
	// }
}
